package scriptmanager

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"strings"
	"sync"

	"github.com/dop251/goja"
	"github.com/wonderivan/logger"

	"MqttCloudProtocolServer/ProtocolServer/scriptmanager/jscontrol"
)

var compiledJSFile map[string]*goja.Program
var jsManagerLock sync.Mutex

//JavaScriptManager js脚本管理器。模块名必须与文件名相同
// 必须实现的控制接口：
// new				js函数形式：new(string param) (bool res,string err)
// Start			js函数形式：Start() (bool res,string err)
// Stop				js函数形式：Stop() (bool res,string err)
//
// 可选的事件接口：
// MQTTReciveEvent	js函数形式：MQTTReciveEvent(byte[] buffer) (bool res,string err)
//
// 可供调用的接口函数：
// MQTTSend			js函数形式：MQTTSend(byte[] buffer)(bool res,string error)
type JavaScriptManager struct {
	ScriptBase
	filaPath     string
	jsRuntime    *goja.Runtime
	content      *goja.Object
	initJs       goja.Callable
	startJs      goja.Callable
	stopJs       goja.Callable
	mqttReciveJs goja.Callable
	/*tcpReciveLua  lua.LValue
	tcpAcceptLua  lua.LValue
	timerElapsed  lua.LValue*/
}

//Init 初始化脚本状态和变量，注入接口.
//JS函数形式：Init(string param) (bool res,string err)
func (_this *JavaScriptManager) Init(scriptName string, param string) (bool, error) {
	jsManagerLock.Lock()
	if compiledJSFile == nil {
		compiledJSFile = make(map[string]*goja.Program)
	}
	script, exist := compiledJSFile[scriptName]
	//编译js文件
	if !exist {
		if _, err := os.Lstat(scriptName); os.IsNotExist(err) {
			return false, err
		}
		code, err := ioutil.ReadFile(scriptName)
		if err != nil {
			return false, err
		}
		script, err = goja.Compile(scriptName, string(code), false)
		if err != nil {
			return false, err
		}
		compiledJSFile[scriptName] = script
	}
	jsManagerLock.Unlock()
	vm := goja.New()
	if _, err := vm.RunProgram(script); err != nil {
		return false, err
	}
	_this.jsRuntime = vm

	_, fileName := filepath.Split(scriptName)
	ext := path.Ext(scriptName)
	fileName = strings.TrimRight(fileName, ext)

	obj := vm.Get(fileName)
	objr, err := jscontrol.IsGojaObject(obj, _this.jsRuntime) //_this.isGojaObject(obj)
	if objr == nil {
		return false, err
	}
	obj = vm.CreateObject(objr)
	objr, err = jscontrol.IsGojaObject(obj, _this.jsRuntime)
	if objr == nil {
		return false, err
	}
	_this.content = objr
	_this.injectJSFunc()
	if res, err := _this.exportJsFunc(); !res {
		_this.content = nil
		return false, err
	}
	val, err := _this.initJs(_this.content, _this.jsRuntime.ToValue(param))
	if err != nil {
		return false, err
	}
	return jscontrol.GetResAndErr(val, _this.jsRuntime)
}
func (_this *JavaScriptManager) exportJsFunc() (bool, error) {
	var err error
	if _this.initJs, err = jscontrol.GetJsFunction("Init", _this.content); err != nil {
		return false, err
	}
	if _this.startJs, err = jscontrol.GetJsFunction("Start", _this.content); err != nil {
		return false, err
	}
	if _this.stopJs, err = jscontrol.GetJsFunction("Stop", _this.content); err != nil {
		return false, err
	}
	if _this.mqttReciveJs, err = jscontrol.GetJsFunction("MQTTReciveEvent", _this.content); err != nil {
		logger.Error(_this.filaPath + "MQTTReciveEvent 未加载")
	}
	return true, nil
}

//injectJSFunc 注入js函数接口
func (_this *JavaScriptManager) injectJSFunc() {
	console := _this.jsRuntime.NewObject()
	consolePrintMethod := [5]string{"log", "info", "trace", "debug", "error"}
	for _, mName := range consolePrintMethod {
		console.Set(mName, func(call goja.FunctionCall) goja.Value {
			if len(call.Arguments) < 1 {
				logger.Error("没有需要打印的参数")
				return nil
			}
			str := call.Argument(0)
			fmt.Println(str)
			return str
		})
	}
	_this.jsRuntime.Set("console", console)
	api := _this.jsRuntime.NewObject()
	api.Set("create", func(call goja.FunctionCall) goja.Value {
		if len(call.Arguments) < 1 {
			logger.Error("没有需要创建的模块")
			return nil
		}
		moduleName, err := jscontrol.GetStringFromArgument(call.Argument(0))
		if err != nil {
			logger.Error("没有需要创建的模块")
			return nil
		}
		return _this.createModule(moduleName)
	})
	_this.jsRuntime.Set("api", api)
	if _this.content != nil {
		_this.content.Set("MQTTSend", _this.mqttSendFromJS)
	}
}
func (_this *JavaScriptManager) createModule(moduleName string) goja.Value {
	switch moduleName {
	case "TCPServer":
		return jscontrol.CreateTCPModule(_this.jsRuntime)
	default:
		logger.Error("未找到模块" + moduleName)
		return nil
	}
}

//Start 启动脚本运行。
//JS函数形式：Start() (bool res,string err)
func (_this *JavaScriptManager) Start() (bool, error) {
	res, err := _this.startJs(_this.content)
	if err != nil {
		return false, err
	}
	return jscontrol.GetResAndErr(res, _this.jsRuntime)
}

//Stop 停止脚本运行。
//JS函数形式：Stop() (bool res,string err)
func (_this *JavaScriptManager) Stop() (bool, error) {
	res, err := _this.stopJs(_this.content)
	if err != nil {
		return false, nil
	}
	return jscontrol.GetResAndErr(res, _this.jsRuntime)
}

//MQTTReciveToScript MQTT收到数据时此函数响应。此函数在接口中，会被父类直接调用.
//JS函数形式：MQTTReciveEvent(byte[] buffer) (bool res,string err)
func (_this *JavaScriptManager) MQTTReciveToScript(buffer []uint8) (bool, error) {
	res, err := _this.mqttReciveJs(_this.content, _this.jsRuntime.ToValue(buffer))
	if err != nil {
		return false, nil
	}
	return jscontrol.GetResAndErr(res, _this.jsRuntime)
}

//mqttSendFromJS MQTT发送数据时应调用此函数
//JS函数形式：MQTTSend(byte[] buffer)(bool res,string error)
func (_this *JavaScriptManager) mqttSendFromJS(call goja.FunctionCall) goja.Value {
	if len(call.Arguments) < 1 {
		return jscontrol.GetFromResAndErr(false, errors.New("Send函数调用参数数量错误"), _this.jsRuntime)
	}
	buffer, err := jscontrol.GetBufferFromArgument(call.Argument(0))
	if err != nil {
		return jscontrol.GetFromResAndErr(false, err, _this.jsRuntime)
	}
	res, err := _this.mqttSender(buffer)
	return jscontrol.GetFromResAndErr(res, err, _this.jsRuntime)
}
